#include   <jni.h>
#include   <stdio.h>    
#include   <stdlib.h>  
#include   <unistd.h>    
#include   <sys/types.h>
#include   <sys/stat.h> 
#include   <fcntl.h>   
#include   <termios.h> 
#include   <errno.h>   
#include   <string.h>
#include   "android/log.h"

const char *JNI_TAG = "fp_pt_jni";
const char *JNI_CLASS =	"com/example/serialporthelper";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  JNI_TAG, fmt, ##args)
//#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, JNI_TAG, fmt, ##args)
#define LOGD(fmt, args...)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, JNI_TAG, fmt, ##args)
#define LOGW(fmt, args...) __android_log_print(ANDROID_LOG_WARN,  JNI_TAG, fmt, ##args)
#define LOGF(fmt, args...) __android_log_print(ANDROID_LOG_FATAL, JNI_TAG, fmt, ##args)

JNIEXPORT jint JNICALL nopen(JNIEnv *, jobject, jstring, jint, jint, jint, jint);
JNIEXPORT void JNICALL nclose(JNIEnv *, jobject, jint);
JNIEXPORT jbyteArray JNICALL nread(JNIEnv *, jobject, jint, jint, jint);
JNIEXPORT jint JNICALL nwrite(JNIEnv *, jobject, jint, jbyteArray);
JNIEXPORT void JNICALL nclear(JNIEnv *, jobject, jint);

static JNINativeMethod methods[] = {
	{"openport", "(Ljava/lang/String;IIII)I", (void *)nopen},
	{"closeport", "(I)V", (void *)nclose},
	{"readport", "(III)[B", (void *)nread},
	{"writeport", "(I[B)I", (void *)nwrite},
	{"clearportbuf", "(I)V", (void *)nclear},
};

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *Methods, int numMethods)
{
	jclass clazz;
	clazz = (*env)->FindClass(env, className);
	if(clazz == NULL)
	{
		LOGE("Can't find jclass: %s", className);
		return JNI_FALSE;
	}
	if((*env)->RegisterNatives(env, clazz, Methods, numMethods) < 0)
	{
		LOGE("Register real action failed %s", className);
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

static int register_func(JNIEnv *env)
{
	if(!registerNativeMethods(env, JNI_CLASS, methods, sizeof(methods) / sizeof(methods[0])))
	{
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
	JNIEnv *env = NULL;
	LOGI("JNI_OnLoad");

	if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)
	{
		LOGE("ERROR: GetEnv failed");
		return -1;
	}

	if(!register_func(env))
	{
		LOGE("ERROR: registerNatives failed");
		return -1;
	}
	return JNI_VERSION_1_4;
}

static speed_t getBaudrate(jint baudrate)
{
	switch(baudrate) {
		case 0: return B0;
		case 50: return B50;
		case 75: return B75;
		case 110: return B110;
		case 134: return B134;
		case 150: return B150;
		case 200: return B200;
		case 300: return B300;
		case 600: return B600;
		case 1200: return B1200;
		case 1800: return B1800;
		case 2400: return B2400;
		case 4800: return B4800;
		case 9600: return B9600;
		case 19200: return B19200;
		case 38400: return B38400;
		case 57600: return B57600;
		case 115200: return B115200;
		case 230400: return B230400;
		case 460800: return B460800;
		case 500000: return B500000;
		case 576000: return B576000;
		case 921600: return B921600;
		case 1000000: return B1000000;
		case 1152000: return B1152000;
		case 1500000: return B1500000;
		case 2000000: return B2000000;
		case 2500000: return B2500000;
		case 3000000: return B3000000;
		case 3500000: return B3500000;
		case 4000000: return B4000000;
		default: return -1;
	}
}

static void setTermios(struct termios * pNewtio, int uBaudRate, int cSize, int cStop, int crc)
{
	bzero(pNewtio, sizeof(struct termios)); /* clear struct for new port settings */
	pNewtio->c_cflag = uBaudRate | cSize | cStop | CREAD | CLOCAL;
	if(crc == 0)
	{
		pNewtio->c_iflag |= IGNPAR;
	}
	else if(crc == 1)
	{
		pNewtio->c_cflag |= PARENB;
		pNewtio->c_iflag |= INPCK;
	}
	else if(crc == 2)
	{
		pNewtio->c_cflag |= (PARENB | PARODD);
		pNewtio->c_iflag |= INPCK;
	}
	pNewtio->c_oflag = 0;
	pNewtio->c_lflag = 0; //non ICANON

	pNewtio->c_cc[VINTR] = 0; /* Ctrl-c */
	pNewtio->c_cc[VQUIT] = 0; /* Ctrl-\ */
	pNewtio->c_cc[VERASE] = 0; /* del */
	pNewtio->c_cc[VKILL] = 0; /* @ */
	pNewtio->c_cc[VEOF] = 4; /* Ctrl-d */
	pNewtio->c_cc[VTIME] = 5; /* inter-character timer, timeout VTIME*0.1 */
	pNewtio->c_cc[VMIN] = 0; /* blocking read until VMIN character arrives */
	pNewtio->c_cc[VSWTC] = 0; /* '\0' */
	pNewtio->c_cc[VSTART] = 0; /* Ctrl-q */
	pNewtio->c_cc[VSTOP] = 0; /* Ctrl-s */
	pNewtio->c_cc[VSUSP] = 0; /* Ctrl-z */
	pNewtio->c_cc[VEOL] = 0; /* '\0' */
	pNewtio->c_cc[VREPRINT] = 0; /* Ctrl-r */
	pNewtio->c_cc[VDISCARD] = 0; /* Ctrl-u */
	pNewtio->c_cc[VWERASE] = 0; /* Ctrl-w */
	pNewtio->c_cc[VLNEXT] = 0; /* Ctrl-v */
	pNewtio->c_cc[VEOL2] = 0; /* '\0' */
}

JNIEXPORT jint JNICALL nopen(JNIEnv *env, jobject obj, jstring port, jint rate, jint bit, jint stop, jint crc)
{
	int fd;
	jboolean iscopy;
	struct termios newtio;
	const char *dev = (*env)->GetStringUTFChars(env, port, &iscopy);;
	fd = open(dev, O_RDWR | O_NOCTTY);
	LOGD("now open %s\n", dev);
	(*env)->ReleaseStringUTFChars(env, port, dev);
	if(fd < 0)
	{
		LOGE("open serial port failed\n");
		LOGE("reason is %s\n", strerror(errno));
		return -1;
	}
	if(getBaudrate(rate) < 0)
	{
		LOGE("speed is not support");
		return -1;
	}
	if((bit != 8) && (bit != 7))
	{
		LOGE("size can only have value 7 or 8");
		return -1;
	}
	if((stop != 1) && (stop != 2))
	{
		LOGE("stop bit can only have value 1 or 2");
		return -1;
	}
	if((crc != 0) && (crc != 1) && (crc != 2))
	{
		LOGE("crc can only be no crc, even crc, odd crc");
		return -1;
	}
	setTermios(&newtio, getBaudrate(rate), (bit == 8 ? CS8 : CS7), (stop == 1 ? 0 : CSTOPB), crc);	// 57600bps 8n1
	tcflush(fd, TCIFLUSH);
	tcsetattr(fd, TCSANOW, &newtio);
	LOGD("open serial port ok\n");
	return fd;
}

JNIEXPORT void JNICALL nclose(JNIEnv *env, jobject obj, jint fd)
{
	(void)close(fd);
	LOGD("close serial port ok\n");
}

JNIEXPORT jbyteArray JNICALL nread(JNIEnv *env, jobject obj, jint fd, jint count, jint delay)
{
	int reval = 0, readed = 0;
	struct timeval tv;
	fd_set rfds;
	jbyteArray jba;
	char *buf = malloc(count);
	do
	{
		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);
		tv.tv_sec = 0;
		tv.tv_usec = delay * 1000;
		if((reval = select(1 + fd, &rfds, NULL, NULL, &tv)) > 0)
		{
			if(FD_ISSET(fd, &rfds))
			{
				int temp = read(fd, buf + readed, count - readed);
				if(temp < 0)
				{
					LOGE("read serial error\n");
					LOGE("reason is %s\n", strerror(errno));
					free(buf);
					return NULL;
				}
				readed += temp;
				LOGD("%i us is used", delay * 1000 - tv.tv_usec);
				LOGD("read %d of %d chars\n", readed, count);
			}
		}
		else if(reval == 0 && readed != 0)			//timeout means 
		{
			break;
		}
		else
		{
			free(buf);
			return NULL;
		}
	}
	while(readed < count);
	jba = (*env)->NewByteArray(env, readed);
	(*env)->SetByteArrayRegion(env, jba, 0, readed, buf);
	free(buf);
	return jba;
//	LOGE("nread fd is %d, %d bytes to read, %d ms to delay", fd, number, delay);
//	return NULL;
}

JNIEXPORT jint JNICALL nwrite(JNIEnv *env, jobject obj, jint fd, jbyteArray content)
{
	int reval = 0, writed = 0, count = 0;
	char *buf = (*env)->GetByteArrayElements(env, content, 0);
	count = (*env)->GetArrayLength(env, content);
	tcflush(fd, TCOFLUSH);		//only need to discard out buffer
	do
	{
		reval = write(fd, buf + writed, count - writed);
		if(reval < 0)
		{
			LOGE("write serial port error\n");
			LOGE("reason is %s\n", strerror(errno));
			(*env)->ReleaseByteArrayElements(env, content, buf, 0);
			return -1;
		}
		writed += reval;
		LOGD("write %d of %d chars\n", writed, count);
	}
	while(writed < count);
	(*env)->ReleaseByteArrayElements(env, content, buf, 0);
	return 0;
}

JNIEXPORT void JNICALL nclear(JNIEnv *env, jobject obj, jint fd)
{
	tcflush(fd, TCIOFLUSH);
}
